#include <algorithm/analytical_model/Constants.h>
#include <algorithm/analytical_model/HeliostatPara.h>
#include <algorithm/analytical_model/HeliostatWithGlassPara.h>
#include <algorithm/analytical_model/ModelPara.h>
#include <algorithm/analytical_model/convolution/ConvolutionPara.h>
#include <algorithm/analytical_model/convolution/HuangPara.h>
#include <algorithm/analytical_model/convolution/ICauchyPara.h>
#include <algorithm/analytical_model/convolution/UNIZARPara.h>
#include <algorithm/analytical_model/simplified/BHFLCALPara.h>
#include <algorithm/analytical_model/simplified/HFLCALPara.h>
#include <algorithm/analytical_model/simplified/IHFLCALPara.h>
#include <algorithm/analytical_model/simplified/NEGPara.h>
#include <algorithm/analytical_model/simplified/SimplifiedPara.h>
#include <ui/window/simulationWindow/AnalyticFunctionWindowAdapter.h>
#include <utils/Concepts.h>
#include <utils/Format.h>
#include <utils/HasValue.h>
#include <utils/ParaUtils.h>
#include <utils/config.h>

#include <QDebug>

#include <cstdint>
#include <exception>

// note: UI上显示字符为空时，无法编辑（也无法添加字符）

using namespace solar;

// Only for test
AnalyticFunctionWindowAdapter::AnalyticFunctionWindowAdapter()
{
    this->rows_changed[AnalyticFunctionWindowEnums::TableViewType::HFLCALBasedTableView] = {};
    this->rows_changed[AnalyticFunctionWindowEnums::TableViewType::UNIZARTableView] = {};
    this->rows_changed[AnalyticFunctionWindowEnums::TableViewType::ICauchyTableView] = {};
    this->rows_changed[AnalyticFunctionWindowEnums::TableViewType::HuangTableView] = {};
    this->rows_changed[AnalyticFunctionWindowEnums::TableViewType::HuangWithGlassTableView] = {};

    auto heliostat_para_0 = std::make_shared<HeliostatPara>();
    SOLAR_SET_VALUE(HeliostatPara, heliostat_id_, 0, *heliostat_para_0.get())
    SOLAR_SET_VALUE(HeliostatPara, receiver_id_, 0, *heliostat_para_0.get())
    this->heliostat_paras_[0] = heliostat_para_0;
    auto icauchy_para_0 = std::make_shared<ICauchyPara>();
    auto convolution_para_0 = std::make_shared<ConvolutionPara>();
    SOLAR_SET_VALUE(ConvolutionPara, model_type_, ConvolutionModelType::kiCauchy,
                    *convolution_para_0.get())
    SOLAR_SET_VALUE(ConvolutionPara, model_para_, icauchy_para_0, *convolution_para_0.get())

    decltype(this->convolution_models_)::mapped_type icauchy_paras;
    icauchy_paras[0] = convolution_para_0;
    this->convolution_models_[ConvolutionModelType::kiCauchy] = icauchy_paras;
}

auto AnalyticFunctionWindowAdapter::getProjectionType() const -> ProjectionType
{
    return this->projection_type_;
}

void AnalyticFunctionWindowAdapter::setProjectionType(const ProjectionType& type)
{
    this->projection_type_ = type;
}

auto AnalyticFunctionWindowAdapter::getModelType() const -> ModelType { return this->model_type_; }

void AnalyticFunctionWindowAdapter::setModelType(const ModelType& type)
{
    this->model_type_ = type;
}

auto AnalyticFunctionWindowAdapter::getSimplifiedModelType() const -> SimplifiedModelType
{
    return this->simplified_model_type_;
}

void AnalyticFunctionWindowAdapter::setSimplifiedModelType(const SimplifiedModelType& type)
{
    this->simplified_model_type_ = type;
}

auto AnalyticFunctionWindowAdapter::getConvolutionModelType() const -> ConvolutionModelType
{
    return this->convolution_model_type_;
}

void AnalyticFunctionWindowAdapter::setConvolutionModelType(const ConvolutionModelType& type)
{
    this->convolution_model_type_ = type;
}

auto AnalyticFunctionWindowAdapter::getHeliostatType() const -> HeliostatType
{
    return this->heliostat_type_;
}

void AnalyticFunctionWindowAdapter::setHeliostatType(const HeliostatType& type)
{
    this->heliostat_type_ = type;
}

auto AnalyticFunctionWindowAdapter::getCommonPara(const QString& key) -> QString
{
    ASSUME(!key.isEmpty());
    if (::solar::paraHasValue(key.toStdString(), std::addressof(this->common_para_)))
    {
        return QString::fromStdString(
            ::solar::getParaAsString(key.toStdString(), std::addressof(this->common_para_)));
    }
    return " ";
}

void AnalyticFunctionWindowAdapter::setCommonPara(const QString& key, const QString& value)
{
    ASSUME(!key.isEmpty());
    if (value.isEmpty() || value == " ") [[unlikely]]
    {
        return;
    }
    try
    {
        ::solar::setParaFromString(key.toStdString(), value.toStdString(),
                                   std::addressof(this->common_para_));
    }
    catch (const std::string& message)
    {
        qDebug() << QString::fromStdString(::solar::format(
            "[ERROR] An error occurred in \"{}\": \n    {}", PRETTY_FUNCTION_NAME, message));
    }
    catch (const std::exception& e)
    {
        qDebug() << QString::fromStdString(::solar::format(
            "[ERROR] An error occurred in \"{}\": \n    {}", PRETTY_FUNCTION_NAME, e.what()));
    }
}

auto AnalyticFunctionWindowAdapter::getScenePara(const QString& key) -> QString
{
    ASSUME(!key.isEmpty());
    if (::solar::paraHasValue(key.toStdString(), std::addressof(this->scene_para_)))
    {
        return QString::fromStdString(
            ::solar::getParaAsString(key.toStdString(), std::addressof(this->scene_para_)));
    }
    return " ";
}

void AnalyticFunctionWindowAdapter::setScenePara(const QString& key, const QString& value)
{
    ASSUME(!key.isEmpty());
    if (value.isEmpty() || value == " ") [[unlikely]]
    {
        return;
    }
    try
    {
        ::solar::setParaFromString(key.toStdString(), value.toStdString(),
                                   std::addressof(this->scene_para_));
    }
    catch (const std::string& message)
    {
        qDebug() << QString::fromStdString(::solar::format(
            "[ERROR] An error occurred in \"{}\": \n    {}", PRETTY_FUNCTION_NAME, message));
    }
    catch (const std::exception& e)
    {
        qDebug() << QString::fromStdString(::solar::format(
            "[ERROR] An error occurred in \"{}\": \n    {}", PRETTY_FUNCTION_NAME, e.what()));
    }
}

auto AnalyticFunctionWindowAdapter::getSimulationPara(const QString& key) -> QString
{
    ASSUME(!key.isEmpty());
    if (::solar::paraHasValue(key.toStdString(), std::addressof(this->simulation_para_)))
    {
        return QString::fromStdString(
            ::solar::getParaAsString(key.toStdString(), std::addressof(this->simulation_para_)));
    }
    return " ";
}

void AnalyticFunctionWindowAdapter::setSimulationPara(const QString& key, const QString& value)
{
    ASSUME(!key.isEmpty());
    if (value.isEmpty() || value == " ") [[unlikely]]
    {
        return;
    }
    try
    {
        ::solar::setParaFromString(key.toStdString(), value.toStdString(),
                                   std::addressof(this->simulation_para_));
    }
    catch (const std::string& message)
    {
        qDebug() << QString::fromStdString(::solar::format(
            "[ERROR] An error occurred in \"{}\": \n    {}", PRETTY_FUNCTION_NAME, message));
    }
    catch (const std::exception& e)
    {
        qDebug() << QString::fromStdString(::solar::format(
            "[ERROR] An error occurred in \"{}\": \n    {}", PRETTY_FUNCTION_NAME, e.what()));
    }
}

auto AnalyticFunctionWindowAdapter::getNumberOfHeliostats() const -> std::uint64_t
{
    return this->heliostat_paras_.size();
}

auto AnalyticFunctionWindowAdapter::getHeliostatIndices() const -> QVariantList
{
    QVariantList list;
    for (const auto& [key, value] : this->heliostat_paras_)
    {
        list.append(key);
    }
    return list;
}

auto AnalyticFunctionWindowAdapter::getHeliostatPara(std::uint64_t heliostat_index,
                                                     const QString& key) -> QString
{
    ASSUME(!key.isEmpty());
    if (this->heliostat_paras_.contains(heliostat_index)) [[likely]]
    {
        if (::solar::paraHasValue(key.toStdString(),
                                  this->heliostat_paras_.at(heliostat_index).get()))
        {
            return QString::fromStdString(::solar::getParaAsString(
                key.toStdString(), this->heliostat_paras_.at(heliostat_index).get()));
        }
    }
    return " ";
}

void AnalyticFunctionWindowAdapter::setHeliostatPara(std::uint64_t heliostat_index,
                                                     const QString& key, const QString& value)
{
    ASSUME(!key.isEmpty());
    if (value.isEmpty() || value == " ") [[unlikely]]
    {
        return;
    }
    if (this->heliostat_paras_.contains(heliostat_index)) [[likely]]
    {
        try
        {
            ::solar::setParaFromString(key.toStdString(), value.toStdString(),
                                       this->heliostat_paras_.at(heliostat_index).get());
        }
        catch (const std::string& message)
        {
            qDebug() << QString::fromStdString(::solar::format(
                "[ERROR] An error occurred in \"{}\": \n    {}", PRETTY_FUNCTION_NAME, message));
        }
        catch (const std::exception& e)
        {
            qDebug() << QString::fromStdString(::solar::format(
                "[ERROR] An error occurred in \"{}\": \n    {}", PRETTY_FUNCTION_NAME, e.what()));
        }
    }
}

auto AnalyticFunctionWindowAdapter::getHeliostatWithGlassPara(std::uint64_t heliostat_index,
                                                              const QString& key) -> QString
{
    ASSUME(!key.isEmpty());
    if (this->heliostat_paras_.contains(heliostat_index)) [[likely]]
    {
        auto* heliostat_ptr = this->heliostat_paras_.at(heliostat_index).get();
        auto* ptr = (SOLAR_GET_VALUE(HeliostatPara, heliostat_glass_para_, *heliostat_ptr)).get();
        if (ptr == nullptr)
        {
            SOLAR_SET_VALUE(HeliostatPara, heliostat_glass_para_,
                            std::make_shared<HeliostatWithGlassPara>(), *heliostat_ptr)
            return " ";
        }
        if (::solar::paraHasValue(key.toStdString(), ptr))
        {
            return QString::fromStdString(::solar::getParaAsString(key.toStdString(), ptr));
        }
    }
    return " ";
}

void AnalyticFunctionWindowAdapter::setHeliostatWithGlassPara(std::uint64_t heliostat_index,
                                                              const QString& key,
                                                              const QString& value)
{
    ASSUME(!key.isEmpty());
    if (value.isEmpty() || value == " ") [[unlikely]]
    {
        return;
    }
    if (this->heliostat_paras_.contains(heliostat_index)) [[likely]]
    {
        try
        {
            auto* heliostat_ptr = this->heliostat_paras_.at(heliostat_index).get();
            auto* ptr =
                (SOLAR_GET_VALUE(HeliostatPara, heliostat_glass_para_, *heliostat_ptr)).get();
            ::solar::setParaFromString(key.toStdString(), value.toStdString(), ptr);
        }
        catch (const std::string& message)
        {
            qDebug() << QString::fromStdString(::solar::format(
                "[ERROR] An error occurred in \"{}\": \n    {}", PRETTY_FUNCTION_NAME, message));
        }
        catch (const std::exception& e)
        {
            qDebug() << QString::fromStdString(::solar::format(
                "[ERROR] An error occurred in \"{}\": \n    {}", PRETTY_FUNCTION_NAME, e.what()));
        }
    }
}

auto AnalyticFunctionWindowAdapter::getModelPara(std::uint64_t heliostat_index,
                                                 ModelType model_type, int detail_model_type,
                                                 const QString& key) -> QString
{
    ASSUME(!key.isEmpty());
    auto get_para_function = [&]<Pointer T>(T ptr) -> QString
    {
        if (::solar::paraHasValue(key.toStdString(), ptr))
        {
            return QString::fromStdString(::solar::getParaAsString(key.toStdString(), ptr));
        }
        return " ";
    };
    if (this->heliostat_paras_.contains(heliostat_index)) [[likely]]
    {
        if (model_type == ModelType::kSimplified)
        {
            auto* raw_ptr = (SOLAR_GET_VALUE(SimplifiedPara, model_para_,
                                             *(this->simplified_models_
                                                   .at(SimplifiedModelType(detail_model_type))
                                                   .at(heliostat_index)
                                                   .get())))
                                .get();
            switch (SimplifiedModelType(detail_model_type))
            {
                using enum solar::SimplifiedModelType;
            case kHFLCAL:
                return get_para_function(dynamic_cast<HFLCALPara*>(raw_ptr));
            case kiHFLCAL:
                return get_para_function(dynamic_cast<IHFLCALPara*>(raw_ptr));
            case kbHFLCAL:
                return get_para_function(dynamic_cast<BHFLCALPara*>(raw_ptr));
            case kNEG:
                return get_para_function(dynamic_cast<NEGPara*>(raw_ptr));
            }
        }
        if (model_type == ModelType::kConvolution)
        {
            auto* raw_ptr = (SOLAR_GET_VALUE(ConvolutionPara, model_para_,
                                             *(this->convolution_models_
                                                   .at(ConvolutionModelType(detail_model_type))
                                                   .at(heliostat_index)
                                                   .get())))
                                .get();
            qDebug() << raw_ptr;
            switch (ConvolutionModelType(detail_model_type))
            {
                using enum solar::ConvolutionModelType;
            case kUNIZAR:
                return get_para_function(dynamic_cast<UNIZARPara*>(raw_ptr));
            case kHuang:
                return get_para_function(dynamic_cast<HuangPara*>(raw_ptr));
            case kiCauchy:
                return get_para_function(dynamic_cast<ICauchyPara*>(raw_ptr));
            }
        }
    }
    return " ";
}

void AnalyticFunctionWindowAdapter::setModelPara(std::uint64_t heliostat_index,
                                                 ModelType model_type, int detail_model_type,
                                                 const QString& key, const QString& value)
{
    ASSUME(!key.isEmpty());
    if (value.isEmpty() || value == " ") [[unlikely]]
    {
        return;
    }
    auto set_para_function = [&]<Pointer T>(T ptr)
    {
        try
        {
            ::solar::setParaFromString(key.toStdString(), value.toStdString(), ptr);
        }
        catch (const std::string& message)
        {
            qDebug() << QString::fromStdString(::solar::format(
                "[ERROR] An error occurred in \"{}\": \n    {}", PRETTY_FUNCTION_NAME, message));
        }
        catch (const std::exception& e)
        {
            qDebug() << QString::fromStdString(::solar::format(
                "[ERROR] An error occurred in \"{}\": \n    {}", PRETTY_FUNCTION_NAME, e.what()));
        }
    };
    if (this->heliostat_paras_.contains(heliostat_index)) [[likely]]
    {
        if (model_type == ModelType::kSimplified)
        {
            auto* raw_ptr = (SOLAR_GET_VALUE(SimplifiedPara, model_para_,
                                             *(this->simplified_models_
                                                   .at(SimplifiedModelType(detail_model_type))
                                                   .at(heliostat_index)
                                                   .get())))
                                .get();
            switch (SimplifiedModelType(detail_model_type))
            {
                using enum solar::SimplifiedModelType;
            case kHFLCAL:
                return set_para_function(dynamic_cast<HFLCALPara*>(raw_ptr));
            case kiHFLCAL:
                return set_para_function(dynamic_cast<IHFLCALPara*>(raw_ptr));
            case kbHFLCAL:
                return set_para_function(dynamic_cast<BHFLCALPara*>(raw_ptr));
            case kNEG:
                return set_para_function(dynamic_cast<NEGPara*>(raw_ptr));
            }
        }
        if (model_type == ModelType::kConvolution)
        {
            auto* raw_ptr = (SOLAR_GET_VALUE(ConvolutionPara, model_para_,
                                             *(this->convolution_models_
                                                   .at(ConvolutionModelType(detail_model_type))
                                                   .at(heliostat_index)
                                                   .get())))
                                .get();
            switch (ConvolutionModelType(detail_model_type))
            {
                using enum solar::ConvolutionModelType;
            case kUNIZAR:
                return set_para_function(dynamic_cast<UNIZARPara*>(raw_ptr));
            case kHuang:
                return set_para_function(dynamic_cast<HuangPara*>(raw_ptr));
            case kiCauchy:
                return set_para_function(dynamic_cast<ICauchyPara*>(raw_ptr));
            }
        }
    }
}

void AnalyticFunctionWindowAdapter::addRowChanged(
    const AnalyticFunctionWindowEnums::TableViewType& type, const std::uint64_t& row)
{
    for (auto& [current_type, current_row] : this->rows_changed)
    {
        if (current_type != type)
        {
            this->rows_changed[current_type].emplace(row);
        }
    }
}

auto AnalyticFunctionWindowAdapter::getRowsChanged(
    const AnalyticFunctionWindowEnums::TableViewType& type) const -> QVariantList
{
    QVariantList list;
    for (const auto& row : this->rows_changed.at(type))
    {
        list.append(row);
    }
    return list;
}

void AnalyticFunctionWindowAdapter::clearRowsChanged(
    const AnalyticFunctionWindowEnums::TableViewType& type)
{
    this->rows_changed[type].clear();
}

auto AnalyticFunctionWindowAdapter::dataChangedFromExternal() const -> bool
{
    return this->data_changed_from_external;
}

void AnalyticFunctionWindowAdapter::dataSyncedWithUI() { this->data_changed_from_external = false; }

namespace solar::internal
{
    const auto to_string_lambda = []<typename T>(T value) -> std::string
    {
        if (solar::hasValue(value))
        {
            return solar::toString(value);
        }
        return " ";
    };
    template <typename... Ts> auto concatStringToQVariantList(Ts... ts) -> QVariantList
    {
        return {(QString::fromStdString(to_string_lambda(ts)))...};
    }
} // namespace solar::internal

auto AnalyticFunctionWindowAdapter::getHFLCALBasedHeliostatParas(std::uint64_t heliostat_index)
    -> QVariantList
{
    auto* heliostat_para_ptr = this->heliostat_paras_.at(heliostat_index).get();
    auto receiver_id = SOLAR_GET_VALUE(HeliostatPara, receiver_id_, *heliostat_para_ptr);
    auto mirror_area = SOLAR_GET_VALUE(HeliostatPara, mirror_area_, *heliostat_para_ptr);
    auto reflectivity = SOLAR_GET_VALUE(HeliostatPara, reflectivity_, *heliostat_para_ptr);
    auto sigma_tracking = SOLAR_GET_VALUE(HeliostatPara, sigma_tracking_, *heliostat_para_ptr);
    auto sigma_slope_error =
        SOLAR_GET_VALUE(HeliostatPara, sigma_slope_error_, *heliostat_para_ptr);
    return internal::concatStringToQVariantList(receiver_id, mirror_area, reflectivity,
                                                sigma_tracking, sigma_slope_error);
}

auto AnalyticFunctionWindowAdapter::getUNIZARHeliostatParas(std::uint64_t heliostat_index)
    -> QVariantList
{
    auto* heliostat_para_ptr = this->heliostat_paras_.at(heliostat_index).get();
    auto receiver_id = SOLAR_GET_VALUE(HeliostatPara, receiver_id_, *heliostat_para_ptr);
    auto mirror_area = SOLAR_GET_VALUE(HeliostatPara, mirror_area_, *heliostat_para_ptr);
    auto total_area = SOLAR_GET_VALUE(HeliostatPara, total_area_, *heliostat_para_ptr);
    return internal::concatStringToQVariantList(receiver_id, mirror_area, total_area);
}

auto AnalyticFunctionWindowAdapter::getHuangHeliostatParas(std::uint64_t heliostat_index)
    -> QVariantList
{
    auto* heliostat_para_ptr = this->heliostat_paras_.at(heliostat_index).get();
    auto receiver_id = SOLAR_GET_VALUE(HeliostatPara, receiver_id_, *heliostat_para_ptr);
    auto mirror_area = SOLAR_GET_VALUE(HeliostatPara, mirror_area_, *heliostat_para_ptr);
    auto reflectivity = SOLAR_GET_VALUE(HeliostatPara, reflectivity_, *heliostat_para_ptr);
    auto sigma_tracking = SOLAR_GET_VALUE(HeliostatPara, sigma_tracking_, *heliostat_para_ptr);
    auto sigma_slope_error =
        SOLAR_GET_VALUE(HeliostatPara, sigma_slope_error_, *heliostat_para_ptr);
    return internal::concatStringToQVariantList(receiver_id, mirror_area, reflectivity,
                                                sigma_tracking, sigma_slope_error);
}

auto AnalyticFunctionWindowAdapter::getHuangWithGlassHeliostatParas(std::uint64_t heliostat_index)
    -> QVariantList
{
    auto* heliostat_para_ptr = this->heliostat_paras_.at(heliostat_index).get();
    auto receiver_id = SOLAR_GET_VALUE(HeliostatPara, receiver_id_, *heliostat_para_ptr);
    auto mirror_area = SOLAR_GET_VALUE(HeliostatPara, mirror_area_, *heliostat_para_ptr);
    auto reflectivity = SOLAR_GET_VALUE(HeliostatPara, reflectivity_, *heliostat_para_ptr);
    auto sigma_tracking = SOLAR_GET_VALUE(HeliostatPara, sigma_tracking_, *heliostat_para_ptr);
    auto sigma_bottom_slope_error =
        SOLAR_GET_VALUE(HeliostatPara, sigma_slope_error_, *heliostat_para_ptr);
    auto* heliostat_with_glass_para_ptr =
        (SOLAR_GET_VALUE(HeliostatPara, heliostat_glass_para_, *heliostat_para_ptr)).get();
    if (heliostat_with_glass_para_ptr == nullptr)
    {
        SOLAR_SET_VALUE(HeliostatPara, heliostat_glass_para_,
                        std::make_shared<HeliostatWithGlassPara>(), *heliostat_para_ptr)
        heliostat_with_glass_para_ptr =
            (SOLAR_GET_VALUE(HeliostatPara, heliostat_glass_para_, *heliostat_para_ptr)).get();
    }
    auto refractivity =
        SOLAR_GET_VALUE(HeliostatWithGlassPara, refractivity_, *heliostat_with_glass_para_ptr);
    auto sigma_surface_slope_error = SOLAR_GET_VALUE(
        HeliostatWithGlassPara, sigma_glass_up_slope_error_, *heliostat_with_glass_para_ptr);
    return internal::concatStringToQVariantList(receiver_id, mirror_area, reflectivity,
                                                sigma_tracking, sigma_bottom_slope_error,
                                                refractivity, sigma_surface_slope_error);
}

auto AnalyticFunctionWindowAdapter::getICauchyHeliostatParas(std::uint64_t heliostat_index)
    -> QVariantList
{
    auto* heliostat_para_ptr = this->heliostat_paras_.at(heliostat_index).get();
    auto receiver_id = SOLAR_GET_VALUE(HeliostatPara, receiver_id_, *heliostat_para_ptr);
    auto mirror_area = SOLAR_GET_VALUE(HeliostatPara, mirror_area_, *heliostat_para_ptr);
    auto reflectivity = SOLAR_GET_VALUE(HeliostatPara, reflectivity_, *heliostat_para_ptr);
    auto* model_para_ptr = dynamic_cast<ICauchyPara*>(
        (SOLAR_GET_VALUE(ConvolutionPara, model_para_,
                         *(this->convolution_models_.at(ConvolutionModelType::kiCauchy)
                               .at(heliostat_index)
                               .get())))
            .get());
    auto diffusion_coeff = SOLAR_GET_VALUE(ICauchyPara, diffusion_coeff_, *model_para_ptr);
    return internal::concatStringToQVariantList(receiver_id, mirror_area, reflectivity,
                                                diffusion_coeff);
}
